////////////////////////////////////////////////////////////////////////////////
//
// labFisDAQ.cc - common code for Lab Fisica experiments
// 
// 1st release: A. Lusiani <a.lusiani@sns.it> April 2003
//
// this program includes parts from poll.c, Part of Comedilib,
// Copyright (c) 1999,2000 David A. Schleef <ds@schleef.org>
// poll.c may be freely modified, distributed, and combined with
// other software, as long as proper attribution is given in the
// source code.
//

////////////////////////////////////////////////////////////////////////////////
//
// headers
//

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/select.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include <comedilib.h>
}

#include "labFisDaq.h"

#define MAX(a,b) ((a)<(b)?(b):(a))

////////////////////////////////////////////////////////////////////////////////
//
// methods of class labFisTermIoHandler
//

//--- default constructor
labFisTermIoHandler::labFisTermIoHandler()
{
  isTerminal = false;
  fn = fileno(stdin);
  //--- init terminal attributes struct private member for stdin
  if (tcgetattr(fn, &attr) == 0) {
    //--- annotate whether the file handler corresponds to a terminal
    isTerminal = true;
  }
  printf("isTerminal %d, fn=%d\n", isTerminal?1:0, fn);
}

//--- constructor with file handler argument
labFisTermIoHandler::labFisTermIoHandler(FILE *input)
{
  isTerminal = false;
  fn = fileno(input);
  //--- init terminal attributes struct private member for stdin
  if (tcgetattr(fn, &attr) == 0) {
    //--- annotate whether the file handler corresponds to a terminal
    isTerminal = true;
  }
  printf("isTerminal %d, fn=%d\n", isTerminal?1:0, fn);
}

labFisTermIoHandler::~labFisTermIoHandler()
{
  //--- restore terminal with initial configuration
  if (isTerminal) {
    tcsetattr(fn, TCSAFLUSH, &attr);
  }
}

//--- setup input terminal to get immediately key-presses, with no echo
void labFisTermIoHandler::SetImmedNoecho()
{
  struct termios attrNew;

  if (isTerminal) {
    memcpy(&attrNew, &attr, sizeof(attr));
    //--- echo off
    attrNew.c_lflag &= ~(ECHO);
    //--- do not wait for newline
    attrNew.c_lflag &= ~(ICANON);
    //--- read at least one char
    attrNew.c_cc[VMIN] = 1;
    // set new terminal attrs
    tcsetattr(fn, TCSAFLUSH, &attrNew);
  }
}

void labFisTermIoHandler::Reset()
{
  //--- restore terminal with initial configuration
  if (isTerminal) {
    tcsetattr(fn, TCSAFLUSH, &attr);
  }
}


////////////////////////////////////////////////////////////////////////////////
//
// functions
//

//
// labFisCmdTest
//
// test/prepare comedi scan command
//

//
// comedi_internal_trigger
//
// taken from ao_waveform.c demo
// start specified internal trigger line
//
int comedi_internal_trigger(comedi_t *dev, unsigned int subd,
			    unsigned int trignum)
{
  comedi_insn insn;
  lsampl_t data[1];
  
  memset(&insn, 0, sizeof(comedi_insn));
  insn.insn = INSN_INTTRIG;
  insn.subdev = subd;
  insn.data = data;
  insn.n = 1;
  data[0] = trignum;
  
  return comedi_do_insn(dev, &insn);
}

//
// labFisCmdTest
//
// test/prepare comedi scan command
//
int labFisScanCmdTest(comedi_t *cifh,
		      comedi_cmd *cmdp
		      )
{
  int rc;

  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh, cmdp);
  fprintf(stderr, "1st comedi_command_test rc: %d\n", rc);
  if (rc<0){
    fprintf(stderr, "1st comedi_command_test errno: %d\n", errno);
    comedi_perror("comedi_command_test");
    return rc;
  } else if (rc>0) {
    fprintf(stderr, "command modified by comedi\n");
    dump_cmd(stderr, cmdp);
    rc = comedi_command_test(cifh,cmdp);
    fprintf(stderr,"2nd comedi_command_test rc: %d\n", rc);
    if (rc<0) {
      fprintf(stderr,"2nd comedi_command_test errno: %d\n", errno);
      comedi_perror("comedi_command_test");
      //--- comedi failure, no scan command possible, quit
      return rc;
    } else if (rc>0) {
      dump_cmd(stderr, cmdp);
      fprintf(stderr, "command modified by comedi\n");
      dump_cmd(stderr, cmdp);
      rc = comedi_command_test(cifh,cmdp);
      fprintf(stderr,"3rd comedi_command_test rc: %d\n", rc);
      if (rc!=0) {
	fprintf(stderr,"3rd comedi_command_test errno: %d\n", errno);
	comedi_perror("comedi_command_test");
	//--- comedi failure, no scan command possible, quit
	return rc;
      }
    }
  }
  return 0;
}
  
//
// labFisScanPrepare
//
// prepare comedi command for Analog Input scan
//
  
void labFisScanPrepare(comedi_t *cifh,
		       int subdev,
		       comedi_cmd *cmdp,
		       unsigned int triggerType,
		       unsigned int sampleRate,
		       unsigned int scanType,
		       unsigned int scanNum,
		       unsigned int *chanList,
		       unsigned int chanListLen
		       )
{
  int rc;

  //--- clear cmd struct
  memset(cmdp, 0, sizeof(*cmdp));
  
  /* the subdevice that the command is sent to */
  cmdp->subdev = 	subdev;
  
  /* flags */
  cmdp->flags =		0;
  
  /* each event requires a trigger, which is specified
     by a source and an argument.  For example, to specify
     an external digital line 3 as a source, you would use
     src=TRIG_EXT and arg=3. */
  
  /* In this case, we specify using TRIG_NOW to start
   * acquisition immediately when the command is issued.  
   * The argument of TRIG_NOW is "number of nsec after
   * NOW", but no driver supports it yet.  Also, no driver
   * currently supports using a start_src other than
   * TRIG_NOW.  */
  cmdp->start_src =		triggerType;
  cmdp->start_arg =		0;
  
  /* The timing of the beginning of each scan is controlled
   * by scan_begin.  TRIG_TIMER specifies that scan_start
   * events occur periodically at a rate of scan_begin_arg
   * nanoseconds between scans. */
  cmdp->scan_begin_src =	TRIG_TIMER;
  cmdp->scan_begin_arg =	static_cast<int>(1e9)/sampleRate;
  
  /* The timing between each sample in a scan is controlled
   * by convert.  Like above, TRIG_TIMER specifies that
   * convert events occur periodically at a rate of convert_arg
   * nanoseconds between scans. */
  cmdp->convert_src =	TRIG_TIMER;
  cmdp->convert_arg =	0;
  
  /* The end of each scan is almost always specified using
   * TRIG_COUNT, with the argument being the same as the
   * number of channels in the chanlist.  You could probably
   * find a device that allows something else, but it would
   * be strange. */
  cmdp->scan_end_src =	TRIG_COUNT;
  cmdp->scan_end_arg =	chanListLen;	/* number of channels */
  
  /* The end of acquisition is controlled by stop_src and
   * stop_arg.  The src will typically be TRIG_COUNT or
   * TRIG_NONE.  Specifying TRIG_COUNT will stop acquisition
   * after stop_arg number of scans, or TRIG_NONE will
   * cause acquisition to continue until stopped using
   * comedi_cancel(). */
  cmdp->stop_src =		scanType;
  cmdp->stop_arg =		scanNum;
  
  /* the channel list determined which channels are sampled.
     In general, chanlist_len is the same as scan_end_arg.  Most
     boards require this.  */
  cmdp->chanlist =	chanList;
  cmdp->chanlist_len =	chanListLen;

  fprintf(stderr, "Assembled DAQ command\n");
  dump_cmd(stderr, cmdp);
  rc = comedi_command_test(cifh, cmdp);
  fprintf(stderr, "1st comedi_command_test rc: %d\n", rc);
  if (rc<0){
    fprintf(stderr, "1st comedi_command_test errno: %d\n", errno);
    comedi_perror("comedi_command_test");
    return;
  } else if (rc>0) {
    fprintf(stderr, "command modified by comedi\n");
    dump_cmd(stderr, cmdp);
    rc = comedi_command_test(cifh,cmdp);
    fprintf(stderr,"2nd comedi_command_test rc: %d\n", rc);
    if (rc<0) {
      fprintf(stderr,"2nd comedi_command_test errno: %d\n", errno);
      comedi_perror("comedi_command_test");
      //--- comedi failure, no scan command possible, quit
      exit(1);
    } else if (rc>0) {
      dump_cmd(stderr, cmdp);
      fprintf(stderr, "command modified by comedi\n");
      dump_cmd(stderr, cmdp);
      rc = comedi_command_test(cifh,cmdp);
      fprintf(stderr,"3rd comedi_command_test rc: %d\n", rc);
      if (rc!=0) {
	fprintf(stderr,"3rd comedi_command_test errno: %d\n", errno);
	comedi_perror("comedi_command_test");
	//--- comedi failure, no scan command possible, quit
	exit(1);
      }
    }
  }

  if (cmdp->scan_begin_arg != static_cast<int>(1e9)/sampleRate) {
    fprintf(stderr,"Error: sampling period forced from %d ns to %d ns\n",
	    static_cast<int>(1e9)/sampleRate, cmdp->scan_begin_arg);
    exit(1);
  }

  return;
}

//
// labFisScanRead
// 
// read Analog Inputs sampled by comedi
//
// returns: reason of return
// args:
// - comedi dev handle
// - buffer pointer
// - buffer size in bytes
// - timeout when reading data from comedi device, in us
// 

ssize_t labFisScanRead(comedi_t *cifh, 
		       int subdev,
		       sampl_t *buf, size_t bufSiz,
		       long int timeout, unsigned int timeoutTot)
{
  int rc;
  int go;
  fd_set rdset;
  struct timeval delayTimeout;
  unsigned int timeoutNum;
  
  go = 1;
  timeoutNum = timeoutTot;

  //--- data acquisition loop (can be interrupted by <control-Y>
  while(go){
    //--- setup rdset structure to wait for comedi scans and keystrokes
    FD_ZERO(&rdset);
    FD_SET(comedi_fileno(cifh),&rdset);
    FD_SET(fileno(stdin),&rdset);
    delayTimeout.tv_sec = 0;
    delayTimeout.tv_usec = timeout;

    //--- wait untill either timeout or some data are available
    rc = select(MAX(comedi_fileno(cifh),fileno(stdin))+1,
		&rdset, NULL, NULL, &delayTimeout);

    if (rc<0) {
      perror("select");
      return -LFDAQ_SYSERR;
    }

    //--- standard input has some characters available
    if (FD_ISSET(fileno(stdin), &rdset)) {
      int ch = fgetc(stdin);
      if (ch == EOF) {
	if (feof(stdin)) {
	  clearerr(stdin);
	  ungetc('\004', stdin);
	  return -LFDAQ_STDINRDY;
	} else {
	  return -LFDAQ_SYSERR;
	}
      }
      ungetc(ch, stdin);
      return -LFDAQ_STDINRDY;
    }

    //--- comedi file descriptor became ready
    if (FD_ISSET(comedi_fileno(cifh), &rdset)) {
      // fprintf(stderr,"comedi file descriptor ready\n");

      //--- read available data up to buffer size
      rc = read(comedi_fileno(cifh), buf, bufSiz);
      // fprintf(stderr,"read returned %d\n",rc);
      if (rc<0) {
	if (errno==EAGAIN) {
	  //--- no data available, continue reading (should not happen)
	  fprintf(stderr, "Signal set but no data available, retrying\n");
	  perror("read");
	} else {
	  fprintf(stderr, "Error reading comedi device, quitting\n");
	  perror("read");
	  return -LFDAQ_SYSERR;
	}
      } else {
	//--- rc == 0 ==> EOF, rc > 0 ==> some data has been read
	//--- reset timeout counter
	timeoutNum = timeoutTot;
	return rc;
      }
    }

    //--- no fd is ready, poll comedi device for data
    if (rc == 0) {
      //--- ask for data even if driver buffer is not full
      // fprintf(stderr,"timeout, polling...\n");
      rc = comedi_poll(cifh, subdev);
      // fprintf(stderr,"poll returned %d\n",rc);
      //--- return if timeout happened at least timeoutTot times
      if (timeoutNum && --timeoutNum == 0) {
	return -LFDAQ_TIMEOUT;
      }
      continue;
    }
    
    //--- unknown file descriptor became ready, should never happen
    fprintf(stderr, "labFisDaq: unknown file descriptor ready\n");
#if 0
    fprintf(stderr, "fd on: ");
    for(int fd_i=0; fd_i<FD_SETSIZE; fd_i++) {
      if (FD_ISSET(fd_i, &rdset)) {
	fprintf(stderr,"%d ", fd_i);
      }
    }
    fprintf(stderr, "\n");
#endif
    return -LFDAQ_SYSERR;
  }

  //--- will never reach here
  return -1;
}

//
// cmd_src
//
// decode source field of comedi command
//

char *cmd_src(int src,char *buf)
{
  buf[0]=0;
  
  if(src&TRIG_NONE)strcat(buf,"none|");
  if(src&TRIG_NOW)strcat(buf,"now|");
  if(src&TRIG_FOLLOW)strcat(buf, "follow|");
  if(src&TRIG_TIME)strcat(buf, "time|");
  if(src&TRIG_TIMER)strcat(buf, "timer|");
  if(src&TRIG_COUNT)strcat(buf, "count|");
  if(src&TRIG_EXT)strcat(buf, "ext|");
  if(src&TRIG_INT)strcat(buf, "int|");
#ifdef TRIG_OTHER
  if(src&TRIG_OTHER)strcat(buf, "other|");
#endif
  
  if(strlen(buf)==0){
    sprintf(buf,"unknown(0x%08x)",src);
  }else{
    buf[strlen(buf)-1]=0;
  }
  
  return buf;
}

//
// dump_cmd
//
// decode comedi command
//

void dump_cmd(FILE *out,comedi_cmd *cmd)
{
  char buf[100];
  
  fprintf(out,"--- begin comedi command\n");
  fprintf(out,"start:      %-8s %d\n",
	  cmd_src(cmd->start_src,buf),
	  cmd->start_arg);
  
  fprintf(out,"scan_begin: %-8s %d\n",
	  cmd_src(cmd->scan_begin_src,buf),
	  cmd->scan_begin_arg);
  
  fprintf(out,"convert:    %-8s %d\n",
	  cmd_src(cmd->convert_src,buf),
	  cmd->convert_arg);
  
  fprintf(out,"scan_end:   %-8s %d\n",
	  cmd_src(cmd->scan_end_src,buf),
	  cmd->scan_end_arg);
  
  fprintf(out,"stop:       %-8s %d\n",
	  cmd_src(cmd->stop_src,buf),
	  cmd->stop_arg);
  fprintf(out,"--- end comedi command\n");
}

//--- read a comedi analog input channel
double labFisAIRead(comedi_t *cifh,
		    int subdev,
		    unsigned int channel,
		    unsigned int ranget,
		    comedi_range* rangep,
		    lsampl_t maxdata,
		    unsigned int aref)
{
  lsampl_t data;
  int rc;

  rc = comedi_data_read(cifh, subdev, channel, ranget, aref, &data);
  if (rc<0){
    comedi_perror("comedi_read_data");
    exit(1);
  }
  return comedi_to_phys(data, rangep, maxdata);
}
